#!/usr/bin/python
#-*- Coding: UTF-8 -*-

import xmlrpclib, sys
try:
 	import pygtk
  	pygtk.require("2.0")
except:
  	pass
try:
	import gtk
  	import gtk.glade
  	import gobject
except:
	sys.exit(1)
import os
#from mplayer import *
import pygst
pygst.require("0.10")
import gst

#import xmlrpclib, sys
#s = xmlrpclib.Server('http://192.168.2.1:7080/',allow_none=True)

class Gui:
    max = 0
    media =[]
    audio_streams=[]
    current_audio = 0
    host = ''
    width=height=0
    par = None
    def __init__(self):
        self.server = None
        self.gladefile = "gui.glade"  
        self.wTree = gtk.glade.XML(self.gladefile)
        #Get the Main Window, and connect the "destroy" event
        self.window = self.wTree.get_widget("window")
        if (self.window):
            self.window.connect("destroy", self.destroy)
        self.window.set_property("allow-grow",True)
        self.window.set_property("allow-shrink",True)
        #self.treeview = self.wTree.get_widget("treeview1")
        self.vbox2 = self.wTree.get_widget("vbox1")
        self.vwidget = VideoWidget(self)
        self.vwidget.show_all()
        self.vbox2.pack_start(self.vwidget,True,True,0)
        self.vbox2.reorder_child(self.vwidget, 1)
        
        #for i in ["prev","play","stop","next"]:
        #    self.wTree.get_widget("button"+i).set_size_request(40,40)
        
        #GStreamer        
        self.player = gst.Pipeline("player")
        self.source = gst.element_factory_make("tcpclientsrc", "data-source")
        #self.source.set_property("blocksize",2*1024*1024)
        #self.source.set_property("protocol",1)
        #self.source.set_property("num-buffers",10)
        #source.set_property("location","/media/data/torrent/Two.and.a.Half.Men.S05E16.HDTV.XviD-XOR.avi")

        self.videosink = gst.element_factory_make("gconfvideosink", "video-sink")
        self.audiosink = gst.element_factory_make("gconfaudiosink", "audio-output")

        self.decodebin = gst.element_factory_make("decodebin", "decoder")
        self.decodebin.connect("new-decoded-pad", self.new_decode_pad)
        self.decodebin.connect("removed-decoded-pad", self.removed_decode_pad)
        
        self.selecta = gst.element_factory_make("input-selector", "selecta")
        self.segment = gst.element_factory_make("identity", "identity-segment")
        self.segment.set_property("sync",True)
        
        #self.audiotest = gst.element_factory_make("audiotestsrc", "test1")
        #self.audiotest.set_property("freq",100)
        self.audioconv = gst.element_factory_make("audioconvert", "converter")
        self.ffmpeg1 = gst.element_factory_make("ffmpegcolorspace", "ffmpeg1")
        self.text1 = gst.element_factory_make("textoverlay", "text1")
        self.deint = gst.element_factory_make("deinterlace", "ffdeint")        
        self.deint.set_property("blend",True)
        self.volume = gst.element_factory_make("volume", "vol")
        self.queuea = gst.element_factory_make("queue", "queuea")
        #self.adder0 = gst.element_factory_make("adder", "adder0")
        self.queuev = gst.element_factory_make("queue", "queuev")
        
        self.player.add(self.source, self.queuea, self.queuev,self.ffmpeg1,self.text1, 
            self.videosink, self.audiosink, self.audioconv,self.decodebin,self.deint,
            self.volume,
            self.selecta,self.segment)
        
        self.text1.set_property("text","Blablabla")
        self.text1.set_property("halignment",2)
        self.text1.set_property("valignment",2)
        
        #source.link(self.decodebin)
        #self.audioconv.link()
        gst.element_link_many(self.source,self.decodebin)
        gst.element_link_many(self.queuev,self.deint,self.ffmpeg1,self.text1,self.videosink)
        gst.element_link_many(self.selecta,self.segment,self.queuea,
            self.volume,
            self.audioconv,self.audiosink)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("message::tag", self.on_tag_message)
        bus.connect("sync-message::element", self.on_sync_message)
        
        self.time_format = gst.Format(gst.FORMAT_TIME)
        
        #self.add_columns()
        #self.add_data(["a","b","c"])

        dic = { "on_open_activate" : self.on_open,
            #"on_ok_clicked" : self.on_open,
            "on_cancel_clicked" : self.on_close_dialog,
            "on_quit_activate" : gtk.main_quit,
            "on_buttonprev_clicked" : self.on_prev,
            "on_buttonplay_clicked" : self.on_play,
            "on_buttonstop_clicked" : self.on_stop,
            "on_buttonnext_clicked" : self.on_next,
            "on_volume_value_changed" : self.on_vol_changed}
            

        self.wTree.signal_autoconnect(dic)
        self.window.show_all()
        gtk.gdk.notify_startup_complete()

        #self.vwidget.varea.window.set_cursor(self.new_cursor())
        #self.player.set_state(gst.STATE_PLAYING)        

    def new_cursor(self):
        pix_data = """/* XPM */
            static char * invisible_xpm[] = {
            "1 1 1 1",
            "       c None",
            " "};"""
        color = gtk.gdk.Color()
        pix = gtk.gdk.pixmap_create_from_data(None,
          pix_data, 1, 1, 1, color, color)
        return gtk.gdk.Cursor(pix, pix, color, color, 0, 0)


    def demuxer_callback(self, demuxer, pad):
        if pad.get_property("template").name_template == "video_%02d":
            queuev_pad = self.queuev.get_pad("sink")
            pad.link(queuev_pad)
        elif pad.get_property("template").name_template == "audio_%02d":
            queuea_pad = self.queuea.get_pad("sink")
            pad.link(queuea_pad)

    # Callback for the decodebin source pad
    def new_decode_pad(self,dbin, pad, islast):
        caps = pad.get_caps()#pad.get_target().get_pad_template_caps()
        if str(caps).startswith("video"):
            pad.link(self.queuev.get_pad("sink"))
        elif str(caps).startswith("audio"):
            #self.audio_streams.append(pad)
            #nr = int(str(pad.get_name()).rsplit("src")[1])
            #self.audio_streams.append(nr)
            print "Add:",pad, self.audio_streams
            pad.link(self.selecta.get_pad("sink%d"%self.current_audio))
            self.current_audio += 1

    # Callback for the decodebin source pad
    def removed_decode_pad(self,dbin, pad):
        caps = pad.get_caps()
        if str(caps).startswith("audio"):
            print "Remove:",pad, pad.get_target()
            remove = None
            self.current_audio -= 1
            '''
            for i in self.selecta.sink_pads():
                if pad.unlink(i):
                    remove = i
                    
            if remove:
                print "Remove sink:",self.selecta.remove_pad(remove)
                print self.selecta.get_property("n-pads")
            '''
            
                
    def toggle_audio(self):
        #k=0
        #self.player.set_state(gst.STATE_PAUSED)
        #pads = self.decodebin.src_pads()
        print "Pads:",self.audio_streams
        print "Switching"
        active_pad = self.selecta.get_property("active-pad")
        npads = self.selecta.get_property("n-pads")
        print npads, active_pad, active_pad.get_name()
        nr = int(str(active_pad.get_name()).rsplit("sink")[1])
        #idx = self.audio_streams.index(nr)
        idx = ((nr + 1) % npads) % self.current_audio
        print "New pad: sink%d"%idx
        self.selecta.set_property("active-pad",self.selecta.get_static_pad("sink%d"%idx))
                
    def get_decoder_src(self, nr):
        for i in self.player.get_by_name("decoder").src_pads():
            structure_name = i.get_caps()[0].get_name()
            if structure_name.startswith("audio") and str(i).find(".src%d"%nr) > -1:
                return i
        
                
    def destroy(self, widget=None):
        self.player.set_state(gst.STATE_NULL)
        gtk.main_quit()

    def responseToDialog(self,entry, dialog, response):  
        dialog.response(response)  
        
    def getText(self):
        text = None
        #base this on a message dialog  
        dialog = gtk.MessageDialog(  
            self.window,  
            gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,  
            gtk.MESSAGE_QUESTION,  
            gtk.BUTTONS_OK_CANCEL,
            None)  
        dialog.set_markup('Please enter server <b>&lt;host:ip&gt;</b>:')  
        #create the text input field  
        entry = gtk.Entry()
        entry.set_text("192.168.2.1:7080")
        #allow the user to press enter to do ok  
        entry.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
        #create a horizontal box to pack the entry and a label  
        hbox = gtk.HBox()  
        hbox.pack_start(gtk.Label("Address:"), False, 5, 5)  
        hbox.pack_end(entry)  
        #some secondary text  
        dialog.format_secondary_markup("This server has <i>XMLRPC + GStreamer</i> service.")
        #add it and show it  
        dialog.vbox.pack_end(hbox, True, True, 0)  
        dialog.show_all()
        #go go go  
        if dialog.run()  == gtk.RESPONSE_OK:
            text = entry.get_text()              
        dialog.destroy()  
        return text
        
        
    def showError(self,msg):
        text = None
        #base this on a message dialog  
        dialog = gtk.MessageDialog(  
            self.window,  
            gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,  
            gtk.MESSAGE_ERROR,  
            gtk.BUTTONS_OK,
            None)  
        dialog.set_markup('An error has occured:')
        dialog.format_secondary_markup("<b>%s</b>"%msg)
        dialog.show_all()
        #go go go  
        dialog.run()
        dialog.destroy()
        
    def on_open(self,w):
        #self.wTree.get_widget("dialog1").hide()
        uri = self.getText() #self.wTree.get_widget("entryserver").get_text()
        if not uri:return
        self.host = uri.split(':',1)[0]
        
        self.server = xmlrpclib.Server('http://%s'%uri,allow_none=True)
        try:
            self.media = self.server.list()
        except Exception, e:
            self.showError(e[1])
            return
        #self.max = len(self.media)
        self.source.set_property('host', self.host )
        self.source.set_property('port',self.server.port())
        #self.connect_stream()

        #self.host = "%s:%s"%( self.host , self.server.port())

    def on_close_dialog(self,w):
        self.wTree.get_widget("dialog1").hide()
        
    def on_show_open(self,w):
        #self.wTree.get_widget("entryserver").set_text("192.168.2.1:7080")
        #self.wTree.get_widget("dialog1").show_all()
        pass
        
    def connect_stream(self):
        self.player.set_state(gst.STATE_NULL)
        #self.player.set_property('host', self.host )
        #self.player.set_property('port',self.server.port())
        self.player.set_state(gst.STATE_PLAYING)
        self.text1.set_property("text",self.server.title())
        
    def on_vol_changed(self,w):
        self.set_vol(w.get_value())
    
    def set_vol(self,vol = None):
        if not vol:
            vol = self.wTree.get_widget("volume").get_value()
        self.volume.set_property("volume",vol)
        
    def on_prev(self,w):
        self.server.prev()
        self.connect_stream()
            
    def on_next(self,w):
        self.server.next()
        self.connect_stream()
            
    def on_play(self,w):
        if self.server.state() == 0:
            self.server.play(None)
        self.connect_stream()
        
    def on_stop(self,w):
        self.server.stop()
        self.player.set_state(gst.STATE_NULL)
        

    def on_tag_message(self, bus, message):
        tags = message.parse_tag()
        for x in tags.keys():
            print x,"=",tags[x]
        #    if x != "private-id3v2-frame":
        #        self.metadata[x] = tags[x]
    
    def on_message(self, bus, message):
        t = message.type
        #print "Message of",t
        if t == gst.MESSAGE_STATE_CHANGED:
            old,new,pending = message.parse_state_changed()
            if new == gst.STATE_PAUSED:
                for i in self.ffmpeg1.src_pads():
                    structure_name = i.get_caps()[0].get_name()
                    if structure_name.startswith("video") and len(str(i.get_caps()[0]["width"])) < 6:
                        self.width,self.height = i.get_caps()[0]["width"], i.get_caps()[0]["height"]
                        self.par = i.get_caps()[0]["pixel-aspect-ratio"]
                        #print "Aspect:",self.par.num,"/",self.par.denom
                        #print "Width:%s, Height:%s" %(self.width,self.height)
                        #self.video_size(self.width,self.height)

        elif t == gst.MESSAGE_EOS:
            self.player.set_state(gst.STATE_NULL)
            
        elif t == gst.MESSAGE_ERROR:
            self.player.set_state(gst.STATE_NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            print message
            
        elif t == gst.MESSAGE_BUFFERING:
            print "%d%%"%message.parse_buffering()
            
                        

    def video_size(self,w,h):
        vw,vh = self.vwidget.window.get_geometry()[2:4]
        ww,wh = self.window.get_size()
        #if w> vw or h>vh:
        if self.par:
            
            if h%self.par.denom != 0:
                h = h / ( float(self.par.num) / self.par.denom )
            #elif w%self.par.num != 0:
            #    w = w / ( float(self.par.num) / self.par.denom )
                
        self.window.resize(int(ww +w -vw), int(wh + h -vh) )
        print self.vwidget.window.get_geometry()[2:4]

    def zoom(self, z):
        self.video_size(self.width* z, self.height*z)
                
    def on_sync_message(self, bus, message):
        if message.structure is None:
            return
        message_name = message.structure.get_name()
        print "Sync:",message_name
        if message_name == "prepare-xwindow-id":
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            self.vwidget.set_sink(imagesink)
            
    def on_popupmenu_clicked(self,w,name):
        if name == "Zoom 50%":
            self.zoom(0.5)
        if name == "Zoom 100%":
            self.zoom(1)
        if name == "Zoom 200%":
            self.zoom(2)
        if name == "Reconnect":
            self.connect_stream()
        if name=="Toggle Audio":
            self.toggle_audio()
            
    def change_visualisation(self,widget,vis_name): 
        #Some not so mature vis plugins hang the pipeline 
        #when changed while in Playing state
        vis = None
        if vis_name!=None:
            #try: 
            vis = gst.element_factory_make(vis_name)
            #except Exception,e: 
            #    vis = None
            
            if vis:
                self.player.set_property('vis-plugin',vis)
        else:
            self.player.set_property('vis-plugin',None)

    def convert_ns(self, time_int):
        time_int = time_int / 1000000000
        sec = time_int % 60
        min = time_int % 3600 / 60
        hour = time_int / 3600
        if hour == 0:
            return "%02d:%02d"%(min,sec)
        return "%d:%02d:%02d"%(hour,min,sec)
        
    def add_columns(self):
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Media', renderer, text=0)
        column.set_clickable(True)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.treeview.append_column(column)

        
    def add_data(self,data, add = False):
        ls = None
        
        if add:
            ls = self.treeview.get_model()
            
        if not add or not ls:
            ls = gtk.ListStore(gobject.TYPE_STRING)
            
        for item in data:
            iter = ls.append()
            ls.set(iter, 0, item)
        
        self.treeview.set_model(ls)

class VideoWidget(gtk.EventBox):
    def __init__(self,gst_player):
        self.gst_player = gst_player
        gtk.EventBox.__init__(self)
        self.connect("button_press_event", self.on_click )
        self.imagesink = None
        self.is_fullscreen = False
        self.varea = VideoArea()
        #self.varea.myinit()
        self.add(self.varea)
        self.visu_plugins = None

        self.loaded = False
        #import thread
        #self.lock = thread.allocate_lock()

    def set_sink(self, sink):
        self.imagesink = sink
        #gobject.idle_add(self.set_window_id)
        self.set_window_id()
        self.child.set_sink(sink)
        if self.is_fullscreen:
            self.fwindow.child.child.set_sink(sink)

        """
        workaround to launch the visualisation on startup
        And prevent the "Xerror GC bad" problem when visualisation start and widget not completey realize
        """
        if not self.loaded:
            self.child.do_expose_event(None)
            self.loaded = True

    def set_window_id(self):
        if self.is_fullscreen:
            self.imagesink.set_xwindow_id(self.fwindow.child.child.window.xid)
        else:
            self.imagesink.set_xwindow_id(self.child.window.xid)

    def on_click(self,btn,event):
        #if self.gst_player.paused:
        #    return
        '''        
        if event.button == 3:
            menu = gtk.Menu()
            i = 0
            for name,detail in self.get_list_plugins():
                label = gtk.Label()
                label.set_markup(detail)
                label.set_alignment(0.0, 0.0)
                item = gtk.MenuItem()
                item.add(label)
                item.connect("activate",self.gst_player.change_visualisation,name)
                menu.attach(item, 0, 1, i, i+1)
                i +=1

            menu.show_all()
            menu.popup(None, None, None, event.button, event.time)
        '''
        if event.button == 3:
            menu = gtk.Menu()
            i = 0
            for name in ["Reconnect","Stop","Zoom 50%","Zoom 100%","Zoom 200%","Toggle Audio"]:
                label = gtk.Label()
                label.set_markup(name)
                label.set_alignment(0.0, 0.0)
                item = gtk.MenuItem()
                item.add(label)
                item.connect("activate",self.gst_player.on_popupmenu_clicked,name)
                menu.attach(item, 0, 1, i, i+1)
                i +=1

            menu.show_all()
            menu.popup(None, None, None, event.button, event.time)
        if event.button == 1:
            self.toggle_fullscreen()


    def toggle_fullscreen(self):
        
        if self.is_fullscreen:
            self.is_fullscreen = False
            #self.imagesink.set_property("force-aspect-ratio", True)
            try:
                self.set_window_id()
            except:
                pass
            self.fwindow.destroy()
        else:
            #self.imagesink.set_property("force-aspect-ratio", False) #Acshully check if vis first, then..
            self.fwindow = gtk.Window(gtk.WINDOW_POPUP)
            self.fwindow.set_decorated(False)
            self.fwindow.set_position(gtk.WIN_POS_CENTER_ALWAYS)
            monitor = gtk.gdk.Screen.get_monitor_geometry(gtk.gdk.screen_get_default(), 0)
            e = gtk.EventBox()
            self.varea = VideoArea(self.imagesink)
            e.add(self.varea)
            e.connect("button_press_event", self.on_click )
            self.fwindow.add(e)
            self.fwindow.resize(monitor.width,monitor.height)
            #self.fwindow.resize(300,300)
            e.show_all()
            self.is_fullscreen = True
            self.fwindow.show_all()
            self.fwindow.set_keep_above(True)
            gtk.gdk.flush()
            self.fwindow.window.set_cursor(self.gst_player.new_cursor())
            self.set_window_id()

    def get_list_plugins(self):
        if self.visu_plugins==None:
            self.visu_plugins = []
            #plugs = gst.registry_get_default().get_plugin_list()
            #for p in plugs:
            #    for elem_factory in gst.registry_get_default().get_feature_list_by_plugin(p.get_name()):
            #        if isinstance(elem_factory,gst.ElementFactory):
            #            if elem_factory.get_klass().rfind("Visualization")!=-1:
            #                self.visu_plugins.append((p.get_name(),elem_factory.get_longname()))
            
            #Because ^^ for some reason didn't work always :S
            r = gst.registry_get_default()
            for x in r.get_feature_list(gst.ElementFactory):
                if (gst.ElementFactory.get_klass(x) == "Visualization"):
                    self.visu_plugins.append((x.get_name(),x.get_longname()))
        return self.visu_plugins

import cairo 
#, rsvg
class VideoArea(gtk.DrawingArea):
    def __init__(self,imagesink=None):
        gtk.DrawingArea.__init__(self)
        self.unset_flags(gtk.DOUBLE_BUFFERED)
        self.imagesink = imagesink
        self.connect("expose_event", self.expose)
        

    def set_sink(self,imagesink):
        self.imagesink = imagesink

    def expose(self, widget, event):
        x,y,w,h = self.get_allocation()
        self.context = widget.window.cairo_create()
        
        # set a clip region for the expose event
        self.context.rectangle(event.area.x, event.area.y,
                               event.area.width, event.area.height)
        self.context.clip()
        self.draw(self.context)
        self.do_expose_event(event)
        return False
    
    def draw(self, context):
        x,y,w,h = self.get_allocation()
        context.set_source_rgb(0,0,0)
        context.rectangle(0,0,w,h)
        context.fill()
        
    def do_expose_event(self, event):
        #self.expose(self,event)
        if self.imagesink:
            #self.imagesink.expose() # keeps showing garbage when 'unfullscreening'
                                     # crashes sometimes, x out of sync
            return False
        else:
            return True

#from __future__ import with_statement
import sys,linecache
def traceit(frame, event, arg):
    if event == "line":
        lineno = frame.f_lineno
        filename = frame.f_globals["__file__"]
        if filename == "<stdin>":
            filename = "glade.py"
        if (filename.endswith(".pyc") or
            filename.endswith(".pyo")):
            filename = filename[:-1]
        name = frame.f_globals["__name__"]
        if name == "__main__":
            line = linecache.getline(filename, lineno)
            print "%s:%s: %s" % (name, lineno, line.rstrip())

    return traceit
    
if __name__ == "__main__":
    #sys.settrace(traceit)
    g = Gui()
    gtk.gdk.threads_init()
    gtk.main()
